# coding:utf-8
# coder:DongLing

import datetime
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

# pdfmetrics.registerFont(TTFont('Kohinoor', '/System/Library/Fonts/Kohinoor.ttc'))
pdfmetrics.registerFont(TTFont('Narrow', 'ttf/songti.ttf'))

from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Paragraph, Image, Table, TableStyle
from reportlab.lib.pagesizes import A4, landscape

from reportlab.lib import pdfencrypt
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.piecharts import Pie

from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.widgets.markers import makeMarker
from reportlab.graphics.charts.barcharts import VerticalBarChart, BarChart

table_style = TableStyle([
    ('FONTNAME', (0, 0), (-1, -1), 'Narrow'),  # 字体
    ('FONTSIZE', (0, 0), (-1, 0), 9),  # 列表头字体大小
    ('FONTSIZE', (0, 1), (-1, -1), 8),  # 列表内容字体大小
    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#F6F6F6")),  # 列表头背景颜色
    ('ALIGN', (0, 0), (-1, 0), "LEFT"),  # 列表头对齐方式
    ("LEFTPADDING", (0, 0), (-1, -1), 1),  # 表格左边内部边距为1
    ("RIGHTPADDING", (0, 0), (-1, -1), 1),  # 表格右边内部边距为1
    ("BOTTOMPADDING", (0, 0), (-1, -1), 1),
    ('ALIGN', (0, 1), (-1, -1), "LEFT"),  # 对齐
    ('TEXTCOLOR', (0, 1), (-1, -1), colors.black),  # 设置表格内文字颜色
    ('GRID', (0, 0), (-1, -1), 0.8, colors.HexColor("#C9C9C9")),
    # 设置表格框线为灰色，线宽为0.5
])


def report_head(report_name, creator_name, create_time, start_time, end_time):
    """
    报表头部信息
    :param report_name:
    :param creator_name:
    :param create_time:
    :param start_time:
    :param end_time:
    :return:
    """
    text = '''
        <para autoLeading="off" leading=14><br/><font face="Narrow" fontSize=10>报表名称: </font><font face="Narrow" fontSize=10>%s</font><br/>
        <font face="Narrow" fontSize=10>创建者: </font><font face="Narrow" fontSize=10>%s</font><br/>
        <font face="Narrow" fontSize=10>创建时间: </font><font face="Narrow" fontSize=10>%s</font><br/>
        <font face="Narrow" fontSize=10>报表时间段: </font><font face="Narrow" fontSize=10>%s ~ %s</font><br/>
        </para>''' % (
        report_name, creator_name, create_time, start_time, end_time)
    return text


def draw_paragraph_row(content, align="center", style_name="Heading3", font="Narrow",
                       font_size=10):
    """
    插入一行
    :param content: 文字内容
    :param align: 对齐方式
    :param style_name: 样式
    :param font: 字体
    :param font_size: 字体大小
    :return:
    """
    text = """<para autoLeading="off" fontSize=%s align=%s><b><font face="%s">%s</font></b></para>""" % (font_size,
                                                                                                         align, font,
                                                                                                         content)

    return Paragraph(text, getSampleStyleSheet()[style_name])


def text_2_paragraph(text, style=getSampleStyleSheet()["Normal"]):
    """
    插入文本行
    :param text:
    :param style:
    :return:
    """
    return Paragraph(text, style)


def drawPageFrame(canvas, doc):
    """
    页眉
    :param canvas:
    :param doc:
    :return:
    """
    h, w = landscape(A4)
    canvas.saveState()
    canvas.drawImage("img/logo.png", 15, h, h - 45, w - 25, 25)
    canvas.restoreState()


def draw_table(table_datas, col_widths):
    """
    画图表
    :param table_datas:
    :param col_widths:
    :return:
    """
    tab = Table(table_datas, colWidths=col_widths)
    tab.setStyle(table_style)
    return tab


def get_pie_image(width, height, x, y, datas, lables, _colors,
                  visable_lable=False):
    """
    生成饼状图
    :param width: 图片宽度
    :param height: 图片的高度
    :param x: 图片的x坐标
    :param y: 图片的y坐标
    :param datas: 生成图片的数据
    :param lables: 饼状图的种类名称
    :param _colors: 颜色集合
    :param visable_lable: 标签是否显示
    :return:
    """
    drawing = Drawing(width, height)
    pc = Pie()
    pc.width = 100
    pc.height = 100
    pc.x = x
    pc.data = datas
    pc.sideLabels = False
    if visable_lable:
        pc.labels = lables
        pc.sideLabels = True
    pc.slices.strokeWidth = 0.8
    pc.checkLabelOverlap = True
    for i in range(len(lables)):
        pc.slices[i].fontName = "Narrow"
        pc.slices[i].fontSize = 8
        pc.slices[i].fillColor = _colors[i]
    drawing.add(pc)
    return drawing


def draw_line():
    from reportlab.graphics.charts.linecharts import HorizontalLineChart
    drawing = Drawing(480, 200)
    data = [
        (13, 5, 20, 22, 37, 45, 19, 4),
        (5, 20, 46, 38, 23, 21, 6, 14)
    ]
    lc = HorizontalLineChart()
    lc.x = 50
    lc.y = 50
    lc.height = 150
    lc.width = 480
    lc.data = data
    lc.joinedLines = 1
    catNames = 'jan Feb Mar Apr May Jun Jul Aug'.split(' ')
    lc.categoryAxis.categoryNames = catNames
    lc.categoryAxis.labels.boxAnchor = 'n'
    lc.valueAxis.valueMin = 0
    lc.valueAxis.valueMax = 60
    lc.valueAxis.valueStep = 15
    lc.lines[0].strokeWidth = 2
    lc.lines[1].strokeWidth = 1.5
    drawing.add(lc)
    return drawing


def get_barchart_lables(width, height, x, y, colors, lables, font_size=8):
    """
    生成柱状图的柱状类图片
    :param width: 图片宽度
    :param height: 图片的高度
    :param x: 图片的x坐标
    :param y: 图片的y坐标
    :param colors: 颜色
    :param lables: 柱状种类名称
    :param font_size: 字体大小
    :return:
    """
    drawing = Drawing(width, height)
    legend = Legend()
    legend.alignment = 'right'
    legend.x = x
    legend.y = y
    legend.dxTextSpace = 2
    # 调用zip方法不进行list()强转的话，会报错
    legend.colorNamePairs = list(zip(colors, lables))
    legend.columnMaximum = 1
    legend.deltax = 60
    legend.fontName = "Narrow"
    legend.fontSize = font_size
    legend.swatchMarker = makeMarker('Circle')
    drawing.add(legend)
    return drawing


def get_barchart_image(width, height, x, y, categoryXlables, datas, barColors,
                       bar_lable_count, max_value=10, lable_width=15):
    """
    生成柱状图
    :param width: 图片宽度
    :param height: 图片的高度
    :param x: 图片的x坐标
    :param y: 图片的y坐标
    :param categoryXlables: 图片的x轴分类名称
    :param datas: 生成图片的数据
    :param barColors: 柱子的颜色集合
    :param bar_lable_count: 柱状图的种类数量
    :param max_value: 柱子的最大值
    :param lable_width: lable标签的最大宽度  超出宽度的字体会省略
    :return:
    """
    _bar_width = get_barchar_barWidth(datas)
    drawing = Drawing(width, height)
    barchart = VerticalBarChart()
    barchart.data = datas
    barchart.x = x
    barchart.y = y
    barchart.width = width - x
    barchart.height = height - y
    barchart.categoryAxis.labels.boxAnchor = 'ne'
    _step = 1 if max_value / 5 == 0 else max_value / 5
    _index = 0
    while max_value > _index * _step:
        _index += 1
    max_value = _index * _step
    barchart.valueAxis.valueMin = 0
    barchart.valueAxis.valueMax = max_value
    barchart.valueAxis.valueStep = _step
    barchart.categoryAxis.labels.dy = -2
    barchart.categoryAxis.labels.angle = 30
    barchart.categoryAxis.labels.fontSize = 8
    barchart.categoryAxis.labels.fontName = 'Narrow'
    barchart.categoryAxis.style = 'stacked'
    for i in range(len(categoryXlables)):
        lable_splits = wrap_str(categoryXlables[i], lable_width).split("\n")
        if len(lable_splits) > 1:
            categoryXlables[i] = "%s..." % (lable_splits[0])
        else:
            categoryXlables[i] = lable_splits[0]
    barchart.categoryAxis.categoryNames = categoryXlables
    if _bar_width:
        barchart.barWidth = _bar_width
    barchart.strokeWidth = 0.1
    barchart.bars.strokeWidth = 0.2
    for i in range(bar_lable_count):
        barchart.bars[i].fillColor = barColors[i]
    drawing.add(barchart, "报表")
    return drawing


def wrap_str(str_value, split_num):
    """
    按指定的字符数换行 中文算1.5个字符宽度数
    :param str_value:
    :param split_num:
    :return:
    """
    if not isinstance(str_value, str):
        str_value = str(str_value)
    long_str = convert_unicode(str_value)
    ret_str = u''
    index = 0
    for uchar in long_str:
        if index >= split_num:
            ret_str += u"\n"
            index = 0
        ret_str += uchar
        if uchar >= u'\u4e00' and uchar <= u'\u9fa5':
            index += 1.6
        elif uchar.isupper():
            index += 1.1
        elif uchar.islower():
            index += 0.85
        elif uchar.isdigit():
            index += 0.9
        elif uchar in [u'#']:
            index += 1
        elif uchar in [u'@', u'-', u'¥', u'-', u'+']:
            index += 1.8
        elif uchar in [u'&', u'%']:
            index += 1.4
        elif uchar in [u'.', u':']:
            index += 0.5
        else:
            index += 0.9
    return ret_str


def convert_unicode(value):
    """
    将字符转换为Unicode格式(中文字符处理)
    :param value:
    :return:
    """
    if not isinstance(value, str):
        try:
            value = value.decode('utf8')
        except UnicodeDecodeError:
            try:
                value = value.decode("gbk")
            except UnicodeDecodeError:
                return None
    return value


def get_barchar_barWidth(barchart_datas):
    _bar_width = None
    if barchart_datas:
        _length = len(barchart_datas[0])
        if _length <= 20 and _length > 10:
            _bar_width = 4
        elif _length <= 10 and _length >= 5:
            _bar_width = 3
        elif _length < 5 and _length > 1:
            _bar_width = 0.8
        elif _length == 1:
            _bar_width = 0.5
    return _bar_width


def draw_image(file_name, width=270, height=100):
    """
    插入图片
    :param file_name:
    :param width:
    :param height:
    :return:
    """
    image = Image(file_name)
    image.drawWidth = width
    image.drawHeight = height
    return image


class PageNumberCanvas(canvas.Canvas):
    """页序号"""

    def __init__(self, *args, **kwargs):
        canvas.Canvas.__init__(self, *args, **kwargs)
        self._saved_page_states = []

    def showPage(self):
        self._saved_page_states.append(dict(self.__dict__))
        self._startPage()

    def draw_page_number(self, page_count):
        self.setFont("Narrow", 10)
        self.drawRightString(110 * mm, 20 * mm,
                             "第%d页/共%d页" % (self._pageNumber, page_count))

    def save(self):
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_number(num_pages)
            canvas.Canvas.showPage(self)
        canvas.Canvas.save(self)


def main():
    datas = []
    create_time = str(datetime.datetime.now())[:19]
    start_time = "2018-04-17 10:54"
    end_time = "2018-04-18 00:00"
    rpt_head = report_head("数据统计报表", "DL", create_time, start_time, end_time)

    # 插入段落
    rpt_title = draw_paragraph_row(u"测试Demo数据统计报表", style_name="Heading1", font_size=15,
                                   font="Narrow")

    datas.append(rpt_title)
    datas.append(text_2_paragraph(rpt_head, getSampleStyleSheet()["Code"]))

    # 表格
    table_datas = [
        ["姓名", "年龄", "手机号", "部门", "工号"],
        ["黄黄", "23", "18570001000", "C++", "DN-100"],
        ["陈陈", "22", "18570001001", "python", "DN-111"],
        ["红红", "20", "18570001002", "Java", "DN-101"],
        ["火火", "19", "18570001003", "Android", "DN-130"],
        ["哈哈", "27", "18570001004", "iOS", "DN-1200"],
        ["呵呵", "30", "18570001005", "web", "DN-001"]
    ]
    tab = draw_table(table_datas, [80, 100, 100, 100, 100])
    none_row = draw_paragraph_row("")
    row_table = draw_paragraph_row("教师信息表")
    datas.append(none_row)
    datas.append(row_table)
    datas.append(tab)

    # 饼图
    pie_data = [3, 1, 2, 2, 4]
    _labels = ['python', 'java', 'iOS', 'js', 'android']
    _colors = [colors.HexColor('#FF0000'),
               colors.HexColor('#FFFF00'),
               colors.HexColor('#32CD32'),
               colors.HexColor('#1E90FF'),
               colors.HexColor('#00FFFF')]
    pie_imag = get_pie_image(480, 120, 220, 40, pie_data, _labels, _colors,
                             visable_lable=True)
    lables = get_barchart_lables(200, 30, 150, 25, _colors, _labels)

    datas.append(draw_paragraph_row("饼图展示"))
    datas.append(pie_imag)
    datas.append(none_row)
    datas.append(lables)

    # 柱状图
    barchart_datas = [
        (40, 5, 20, 22, 37, 45, 19, 4),  # 红色
        (41, 6, 21, 23, 38, 46, 20, 5)  # 黄色
    ]
    barchart_img = get_barchart_image(480, 120, 60, 40,
                                      ['day-%s' % (i) for i in range(1, 9)],
                                      barchart_datas,
                                      _colors[0:2], 2, 100)

    datas.append(none_row)
    datas.append(draw_paragraph_row("柱状图展示"))
    datas.append(barchart_img)

    # 折线图
    datas.append(none_row)
    datas.append(draw_paragraph_row("折线图展示"))
    line_img = draw_line()
    datas.append(line_img)

    # 插入图片
    png_1 = draw_image("img/png1.png", 480, 200)
    png_2 = draw_image("img/png2.png", 480, 200)
    png_3 = draw_image("img/png3.png", 480, 200)
    png_4 = draw_image("img/png4.png", 400, 200)
    png_5 = draw_image("img/png5.png", 400, 200)
    png_tf = draw_image("img/tf.png", 480, 300)
    png_weather = draw_image("img/weather.png", 480, 300)
    datas.append(draw_paragraph_row("百度流量统计"))
    datas.append(png_1)
    datas.append(png_2)
    datas.append(draw_paragraph_row("天气状况"))
    datas.append(png_tf)
    datas.append(png_weather)
    datas.append(draw_paragraph_row("热点分布"))
    datas.append(png_3)
    datas.append(draw_paragraph_row("3D图形展示"))
    datas.append(png_4)
    datas.append(png_5)

    # 创建文件
    file_name = 'pdf个人练习.pdf'
    title = "DL报表"
    f = open(file_name, "wb")
    doc = SimpleDocTemplate(f, title=title, leftMargin=15, rightMargin=15)

    doc.multiBuild(datas, onFirstPage=drawPageFrame,
                   onLaterPages=drawPageFrame,
                   canvasmaker=PageNumberCanvas)
    f.close()


if __name__ == "__main__":
    main()
